/*
 * Tezad - Classic puzzle game
 * Copyright 2009 Shayne Riley and Paul Maseberg
 *
 * Tezad is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tezad is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Tezad.  If not, see <http://www.gnu.org/licenses/>
 * or write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * To contact the authors on questions or issues regarding the license,
 * you may send an email to <pezads at gmail dot com>
 */

#include "GameWell.h"
#include <gtest/gtest.h>
#include <stdexcept>

using ::std::endl;
using ::std::out_of_range;

namespace
{

// The fixture for testing class GameWell.
class GameWellTest: public testing::Test
{
};

const int DEFAULT_HEIGHT = 22;
const int DEFAULT_WIDTH = 10;
const int DEFAULT_HIDDEN = 2; // the number of rows off the top not to be displayed.

/**
 * Test for proper use of GameWell(totHeight, width, hidden) constructor.
 * It must create a well that is proper dimensions and is empty.
 */
TEST_F(GameWellTest, CtorGood)
{
    GameWell gw = GameWell(DEFAULT_HEIGHT, DEFAULT_WIDTH, DEFAULT_HIDDEN);

    EXPECT_EQ(DEFAULT_HEIGHT, gw.height());
    EXPECT_EQ(DEFAULT_WIDTH, gw.width());
    EXPECT_EQ(DEFAULT_HEIGHT - DEFAULT_HIDDEN, gw.visibleHeight());

    for (int row = 0; row < gw.height(); ++row)
    {
        for (int col = 0; col < gw.width(); ++col)
        {
            EXPECT_EQ(0, gw.brick(row, col));
        }
    }
}

/**
 * Tests an improper use case of GameWell(totHeight, width, hidden) and the
 * appropriate actions are taken.
 */
TEST_F(GameWellTest, CtorBadHeight)
{
    EXPECT_THROW(GameWell gw = GameWell(-1, DEFAULT_WIDTH, DEFAULT_HIDDEN), out_of_range)
    << "Should not be able to go less than 0." << endl;
}

/**
 * Tests an improper use case of GameWell(totHeight, width, hidden) and the
 * appropriate actions are taken.
 */
TEST_F(GameWellTest, CtorBadWidth)
{
    EXPECT_THROW(GameWell gw = GameWell(DEFAULT_HEIGHT, -1, DEFAULT_HIDDEN), out_of_range)
    << "Should not be able to go less than 0." << endl;
}

/**
 * Tests an improper use case of GameWell(totHeight, width, hidden) and the
 * appropriate actions are taken.
 */
TEST_F(GameWellTest, CtorBadHidden)
{
    EXPECT_THROW(GameWell gw = GameWell(DEFAULT_HEIGHT, DEFAULT_WIDTH, -1), out_of_range)
    << "Should not be able to go less than 0." << endl;

    EXPECT_THROW(GameWell gw = GameWell(DEFAULT_HEIGHT, DEFAULT_WIDTH, 23), out_of_range)
    << "Should not be able to go greater than total height." << endl;
}

/**
 * Tests that copy constructor properly copies the data of the well such that
 * the information is, in fact, the same, but changes to the original's well
 * does not affect the copy and vice versa.
 */
TEST_F(GameWellTest, CopyCtor)
{
    GameWell orig = GameWell(DEFAULT_HEIGHT, DEFAULT_WIDTH, DEFAULT_HIDDEN);

    // Set one brick before the copy so we know it is copying everything.
    orig.setBrick(DEFAULT_HEIGHT - 1, DEFAULT_WIDTH - 1, 1);

    GameWell copy = GameWell(orig);

    EXPECT_EQ(orig.height(), copy.height());
    EXPECT_EQ(orig.width(), copy.width());
    EXPECT_EQ(orig.visibleHeight(), copy.visibleHeight());

    for (int row = 0; row < copy.height(); ++row)
    {
        for (int col = 0; col < copy.width(); ++col)
        {
            EXPECT_EQ(orig.brick(row, col), copy.brick(row, col))
            << "The well needs to be properly copied." << endl;
        }
    }

    // Change one brick in the copy. It should not change the brick in orig.
    copy.setBrick(0, 0, 2);
    EXPECT_NE(orig.brick(0, 0), copy.brick(0, 0))
    << "Copy shouldn't modify original." << endl;

}

/**
 * Test the class for const correctness: is it still usable when it is a const?
 * This isn't really supposed to be in unit tests, I would think, since it is
 * determined at compile-time rather than run-time, but I need the error to
 * appear somewhere, right?
 */
TEST_F(GameWellTest, ConstCorrect)
{
    const GameWell orig = GameWell(DEFAULT_HEIGHT, DEFAULT_WIDTH, DEFAULT_HIDDEN);

    // Can I still get the state of the well, since it won't change anything?
    // If the compiler complains on this, you'd better believe it is a bug.
    orig.width();
    orig.height();
    orig.visibleHeight();
    orig.brick(0, 0);
}

/**
 * Test Assignment operator. Check that all the variables got changed, that
 * the dynamic memory was deep copied, the pointer is pointing to the correct
 * address, and also that self-assignment is handled properly.
 */
TEST_F(GameWellTest, Assignment)
{
    GameWell orig = GameWell(DEFAULT_HEIGHT, DEFAULT_WIDTH, DEFAULT_HIDDEN);

    // Set one brick before the copy so we know it is copying everything.
    orig.setBrick(DEFAULT_HEIGHT - 1, DEFAULT_WIDTH - 1, 7);

    // Different sizes on everything to tell if something wasn't copied over.
    GameWell dest = GameWell(DEFAULT_HEIGHT + 10, DEFAULT_WIDTH + 10, DEFAULT_HIDDEN + 10);

    // Fill it with different data too! Why? because I'm a jerk.
    for (int row = 0; row < dest.height(); ++row)
    {
        for (int col = 0; col < dest.width(); ++col)
        {
            dest.setBrick(row, col, 2);
        }
    }

    // Now do the copy test!
    dest = orig;

    EXPECT_EQ(orig.height(), dest.height());
    EXPECT_EQ(orig.width(), dest.width());
    EXPECT_EQ(orig.visibleHeight(), dest.visibleHeight());

    for (int row = 0; row < dest.height(); ++row)
    {
        for (int col = 0; col < dest.width(); ++col)
        {
            EXPECT_EQ(orig.brick(row, col), dest.brick(row, col))
            << "The well needs to be properly copied." << endl;
        }
    }

    // Change one brick in the copy. It should not change the brick in orig.
    int destBrick = 2;
    dest.setBrick(0, 0, destBrick);
    EXPECT_NE(orig.brick(0, 0), dest.brick(0, 0))
    << "Copy shouldn't modify original." << endl;


    // TEST 2: Make sure that the correct pointer is received.
    GameWell dest2 = GameWell(DEFAULT_HEIGHT + 1, DEFAULT_WIDTH + 1, DEFAULT_HIDDEN + 1);

    GameWell *pDest2 = &dest2;
    GameWell *pDest2Post = &(dest2 = orig);

    EXPECT_EQ(pDest2, pDest2Post);

    // TEST 3: Self-assignment. I check this by referencing a brick value I
    // changed earlier, which could be different if the method deleted the
    // heap reference and then created a new heap. Granted, this would not
    // be an accurate check if a secondary pointer to the new heap reference
    // was made, data copied into it, and then the old reference is deleted
    // before the new data was pointed at by the memeber variable. So what?
    dest = dest;
    EXPECT_EQ(destBrick, dest.brick(0, 0))
        << "The self-assignment check needs implemented." << endl;
}

/**
 * Tests that width() will return correct values for differently sized wells.
 */
TEST_F(GameWellTest, Width)
{
    GameWell *gw = new GameWell(DEFAULT_HEIGHT, 1, DEFAULT_HIDDEN);
    EXPECT_EQ(1, gw->width());
    delete gw;

    gw = new GameWell(DEFAULT_HEIGHT, 256, DEFAULT_HIDDEN);
    EXPECT_EQ(256, gw->width());
    delete gw;
}

/**
 * Tests that height() will return correct values for differently sized wells.
 */
TEST_F(GameWellTest, Height)
{
    GameWell *gw = new GameWell(1, DEFAULT_WIDTH, 0);
    EXPECT_EQ(1, gw->height());
    delete gw;

    gw = new GameWell(256, DEFAULT_WIDTH, DEFAULT_HIDDEN);
    EXPECT_EQ(256, gw->height());
    delete gw;
}

/**
 * Tests that visibleHeight() will return correct values for differently sized wells.
 */
TEST_F(GameWellTest, VisibleHeight)
{
    GameWell *gw = new GameWell(1, DEFAULT_WIDTH, 0);
    EXPECT_EQ(1, gw->visibleHeight());
    delete gw;

    // None can be hidden.
    gw = new GameWell(256, DEFAULT_WIDTH, 0);
    EXPECT_EQ(256, gw->visibleHeight());
    delete gw;

    // One can be hidden.
    gw = new GameWell(256, DEFAULT_WIDTH, 1);
    EXPECT_EQ(255, gw->visibleHeight());
    delete gw;

    // All of it can be hidden.
    gw = new GameWell(256, DEFAULT_WIDTH, 256);
    EXPECT_EQ(0, gw->visibleHeight());
    delete gw;
}

/**
 * Test for proper use of field accessor methods brick(row, col) and
 *  setBrick(row, col, int).
 */
TEST_F(GameWellTest, AccessorBrick)
{
    GameWell *gw = new GameWell(DEFAULT_HEIGHT, DEFAULT_WIDTH, DEFAULT_HIDDEN);

    // Test 1: Placement of a single brick.
    gw->setBrick(4, 5, 1);
    EXPECT_EQ(1, gw->brick(4, 5));

    // Test 2: More bricks.
    gw->setBrick(0, 0, 7);
    gw->setBrick(DEFAULT_HEIGHT - 1, DEFAULT_WIDTH - 1, 7);
    EXPECT_EQ(7, gw->brick(0, 0));
    EXPECT_EQ(7, gw->brick(DEFAULT_HEIGHT - 1, DEFAULT_WIDTH - 1));

    // Test 3: Test a few other locations to make sure they haven't changed.
    EXPECT_EQ(0, gw->brick(5, 4)) << "Somehow row 5, col 4 changed." << endl;
    EXPECT_EQ(0, gw->brick(0, DEFAULT_WIDTH - 1));


}

/**
 * Test for improper use of field accessor method setBrick(row, col, int).
 */
TEST_F(GameWellTest, BadSetBrick)
{
    GameWell *gw = new GameWell(DEFAULT_HEIGHT, DEFAULT_WIDTH, DEFAULT_HIDDEN);

    // Test 1: Attempted placement of brick to outer-bottom side of well.
    EXPECT_THROW(gw->setBrick(-1, DEFAULT_WIDTH / 2, 1), out_of_range)
            << "Should not be able to set brick on outer-bottom side of well." << endl;

    // Test 2: Attempted placement of brick to outer-top side of well.
    EXPECT_THROW(gw->setBrick(DEFAULT_HEIGHT, DEFAULT_WIDTH / 2, 2), out_of_range)
            << "Should not be able to set brick on outer-top side of well." << endl;

    // Test 3: Attempted placement of brick to outer-left side of well.
    EXPECT_THROW(gw->setBrick(DEFAULT_HEIGHT / 2, -1, 3), out_of_range)
            << "Should not be able to set brick on outer-left side of well." << endl;

    // Test 4: Attempted placement of brick to outer-right side of well.
    EXPECT_THROW(gw->setBrick(DEFAULT_HEIGHT / 2, DEFAULT_WIDTH, 4), out_of_range)
            << "Should not be able to set brick on outer-right side of well." << endl;
}

/**
 * Test for improper use of field accessor method brick(row, col).
 */
TEST_F(GameWellTest, BadBrickAccessor)
{
    GameWell *gw = new GameWell(DEFAULT_HEIGHT, DEFAULT_WIDTH, DEFAULT_HIDDEN);

    // Test 1: Attempted placement of brick to outer-bottom side of well.
    EXPECT_THROW(gw->brick(-1, DEFAULT_WIDTH / 2), out_of_range)
            << "Should not be able to get brick on outer-bottom side of well." << endl;

    // Test 2: Attempted placement of brick to outer-top side of well.
    EXPECT_THROW(gw->brick(DEFAULT_HEIGHT, DEFAULT_WIDTH / 2), out_of_range)
            << "Should not be able to get brick on outer-top side of well." << endl;

    // Test 3: Attempted placement of brick to outer-left side of well.
    EXPECT_THROW(gw->brick(DEFAULT_HEIGHT / 2, -1), out_of_range)
            << "Should not be able to get brick on outer-left side of well." << endl;

    // Test 4: Attempted placement of brick to outer-right side of well.
    EXPECT_THROW(gw->brick(DEFAULT_HEIGHT / 2, DEFAULT_WIDTH), out_of_range)
            << "Should not be able to get brick on outer-right side of well." << endl;
}

} // namespace
